#include "qe_log.h"
#include "qe_assert.h"
#include "qe_memory.h"
#include "qe_driver.h"
#include "libfdt.h"



QELOG_DOMAIN("ps7-uart");



/* Control Register */
#define PS7_UART_CR_STPBRK       0x00000100  /* Stop transmitter break */
#define PS7_UART_CR_STTBRK       0x00000080  /* Start transmitter break */
#define PS7_UART_CR_RSTTO        0x00000040  /* Restart receiver timeout counter */
#define PS7_UART_CR_TXDIS        0x00000020  /* Transmit disable */
#define PS7_UART_CR_TXEN         0x00000010  /* Transmit enable */
#define PS7_UART_CR_RXDIS        0x00000008  /* Receive disable */
#define PS7_UART_CR_RXEN         0x00000004  /* Receive enable */
#define PS7_UART_CR_TXRES        0x00000002  /* Software reset for Tx data path */
#define PS7_UART_CR_RXRES        0x00000001  /* Software reset for Rx data path */

/* Mode Register */
#define PS7_UART_MR_WSIZE_BYTE   0x00001000  /* One byte is always written or read from FIFOs */
#define PS7_UART_MR_CH_ECHO      0x00000100  /* Channel model automatic echo */
#define PS7_UART_MR_CH_LO_LOOP   0x00000200  /* Channel model local loopback */
#define PS7_UART_MR_CH_RE_LOOP   0x00000300  /* Channel model remote loopback */
#define PS7_UART_MR_STOPB_1      0x00000000  /* Stop bit 1 */
#define PS7_UART_MR_STOPB_1_5    0x00000040  /* Stop bit 1.5 */
#define PS7_UART_MR_STOPB_2      0x00000080  /* Stop bit 2 */
#define PS7_UART_MR_PAR_EVE      0x00000000  /* Even parity */
#define PS7_UART_MR_PAR_ODD      0x00000008  /* Odd parity */
#define PS7_UART_MR_PAR_NO       0x00000020  /* No parity */
#define PS7_UART_MR_8_BIT        0x00000000  /* Character length 8 bits */
#define PS7_UART_MR_7_BIT        0x00000004  /* Character length 7 bits */
#define PS7_UART_MR_6_BIT        0x00000006  /* Character length 6 bits */

/* Interrupt Enable Register */
#define PS7_UART_IER_RBRK        QE_BIT(13)  /* Receiver break detect interrupt */
#define PS7_UART_IER_TOVR        QE_BIT(12)  /* Transmitter FIFO Overflow interrupt */
#define PS7_UART_IER_TNFUL       QE_BIT(11)  /* Transmitter FIFO Nearly Full interrupt */
#define PS7_UART_IER_TTRIG       QE_BIT(10)  /* Transmitter FIFO Trigger interrupt */
#define PS7_UART_IER_DMSI        QE_BIT(9)   /* Delta Modem Status Indicator interrupt */
#define PS7_UART_IER_RTMOE       QE_BIT(8)   /* Receiver Timeout Error interrupt */
#define PS7_UART_IER_RPARE       QE_BIT(7)   /* Receiver Parity Error interrupt */
#define PS7_UART_IER_RFRME       QE_BIT(6)   /* Receiver Framing Error interrupt */
#define PS7_UART_IER_ROVRE       QE_BIT(5)   /* Receiver Overflow Error interrupt */
#define PS7_UART_IER_TFUL        QE_BIT(4)   /* Transmitter FIFO Full interrupt */
#define PS7_UART_IER_TEMPTY      QE_BIT(3)   /* Transmitter FIFO Empty interrupt */
#define PS7_UART_IER_RFUL        QE_BIT(2)   /* Receiver FIFO Full interrupt */
#define PS7_UART_IER_REMPTY      QE_BIT(1)   /* Receiver FIFO Empty interrupt */
#define PS7_UART_IER_RTRIG       QE_BIT(0)   /* Receiver FIFO Trigger interrupt: */

#define PS7_UART_SR_TXACTIVE     QE_BIT(11)     /* TX active */
#define PS7_UART_SR_TXFULL       QE_BIT(4)      /* TX FIFO full */
#define PS7_UART_SR_RXEMPTY      QE_BIT(1)      /* RX FIFO empty */

typedef struct
{
    volatile qe_u32 cr;          /* 0x00 Control Register [8:0] */
    volatile qe_u32 mr;          /* 0x04 Mode Register */
    volatile qe_u32 ier;         /* 0x08 Interrupt Enable Register */
    volatile qe_u32 idr;         /* 0x0C Interrupt Disable Register */
    volatile qe_u32 imr;         /* 0x10 Interrupt Mask Register */
    volatile qe_u32 isr;         /* 0x14 Interrupt Status Register */
    volatile qe_u32 brg;         /* 0x18 Baud Rate Generator [15:0] */
    volatile qe_u32 rtm;         /* 0x1C Receiver Timeout Register */
    volatile qe_u32 rftl;        /* 0x20 Receiver FIFO Trigger Level Register */
    volatile qe_u32 reserved2[2];
    volatile qe_u32 sr;          /* 0x2C Channel Status [11:0] */
    volatile qe_u32 fifo;        /* 0x30 FIFO [15:0] or [7:0] */
    volatile qe_u32 brd;         /* 0x34 Buad Rate Divider [7:0] */
} ps7_uart_regs;

typedef struct
{
    qe_serial_dev serial;
    ps7_uart_regs *regs;
    qe_intc_dev *intc;
    qe_uint irq;
    qe_uint clk;
    qe_uint baudrate;
    qe_uint buffer_size;
    int rx_trigger_level;
    int rx_indicate_level;
    int timeout;
    qe_bool is_transfer_started;
} ps7_uart_dev;



static void ps7_uart_irq_enable(ps7_uart_regs *regs, qe_bool en)
{
    qe_u32 mask;

    mask = PS7_UART_IER_RTRIG |
           PS7_UART_IER_RTMOE;

    if (en) {
        regs->ier |= mask;
        regs->idr &= ~mask;
    } else {
        regs->ier &= ~mask;
        regs->idr |= mask;
    }
}

static void ps7_uart_tx_rx_enable(ps7_uart_regs *regs, qe_bool en)
{
    if (en) {
        regs->cr |= PS7_UART_CR_TXEN | PS7_UART_CR_RXEN;
    } else {
        regs->cr &= ~(PS7_UART_CR_TXEN | PS7_UART_CR_RXEN);
    }
}

static void ps7_uart_fifo_reset(ps7_uart_regs *regs)
{
    regs->cr |= PS7_UART_CR_TXRES | PS7_UART_CR_RXRES;
}

static void ps7_uart_set_rx_timeout(ps7_uart_dev *uart, qe_u32 timeout)
{
    ps7_uart_regs *regs = uart->regs;

    /* set timeout value */
    regs->rtm = timeout & 0x000000FF;
    /* restart timeout counter */
    regs->cr |= PS7_UART_CR_RSTTO;
}

static qe_ret ps7_uart_setbrg(ps7_uart_regs *regs, qe_u32 clk, qe_u32 baudrate)
{
    qe_u32 calc_bauderror, bdiv, bgen;
    qe_u32 calc_baud = 0;

    if ((baudrate * 2) > clk) {
        qe_error("baudrate %d error", baudrate);
        return qe_err_param;
    }

	/*                master clk
	 * Baud rate = ------------------
	 *              bgen * (bdiv + 1)
	 *
	 * Find acceptable values for baud generation.
	 */
    for (bdiv=4; bdiv<255; bdiv++) {

        /* Calculate the value for BRGR register */
        bgen = clk / (baudrate * (bdiv + 1));

        /* check if bgen invaild */
        if (bgen < 2 || bgen > 65535) 
            continue;
        
        /* Calculate the baud rate from the BRGR value */
        calc_baud = clk / (bgen * (bdiv + 1));

		/*
		 * Use first calculated baudrate with
		 * an acceptable (<3%) error
		 */
        if (baudrate > calc_baud)
            calc_bauderror = baudrate - calc_baud;
        else
            calc_bauderror = calc_baud - baudrate;
        if (((calc_bauderror * 100) / baudrate) < 3)
            break;
    }

    /* disable uart */
    ps7_uart_tx_rx_enable(regs, qe_false);
    
    regs->brd = bdiv;
    regs->brg = bgen;

    /* reset fifo */
    ps7_uart_fifo_reset(regs);

    /* enable uart */
    ps7_uart_tx_rx_enable(regs, qe_true);
    return qe_ok;
}

static void ps7_uart_hw_init(ps7_uart_dev *uart)
{
    /* RX FIFO trigger 1 bytes */
    uart->regs->rftl = uart->rx_trigger_level;

    ps7_uart_set_rx_timeout(uart, uart->timeout);

    /* enable rx/tx */
    ps7_uart_tx_rx_enable(uart->regs, qe_true);
}

static qe_ret ps7_uart_configure(qe_serial_dev *serial,
    qe_serial_configure *cfg)
{
    qe_u32 mr = 0x0;
    ps7_uart_dev *uart = (ps7_uart_dev *)serial->parent.priv;
    ps7_uart_regs *regs = uart->regs;

    /* disable interrrupt */
    ps7_uart_irq_enable(uart->regs, qe_false);

    /* stop bits */
    if (cfg->stop_bits == STOP_BITS_1) {
        mr |= PS7_UART_MR_STOPB_1;
    } else if (cfg->stop_bits == STOP_BITS_2) {
        mr |= PS7_UART_MR_STOPB_2;
    } else if (cfg->stop_bits != STOP_BITS_1) {
        qe_error("stop bit %d not support", cfg->stop_bits);
        return qe_err_notsupport;
    }

    /* parity */
    if (cfg->parity == PARITY_EVEN) {
        mr |= PS7_UART_MR_PAR_EVE;
    } else if (cfg->parity == PARITY_ODD) {
        mr |= PS7_UART_MR_PAR_ODD;
    } else if (cfg->parity == PARITY_NONE) {
        mr |= PS7_UART_MR_PAR_NO;
    } else {
        qe_error("parity:%d not support", cfg->parity);
        return qe_err_notsupport;
    }

    /* data bits */
    if (cfg->data_bits == DATA_BITS_8) {
        mr |= PS7_UART_MR_8_BIT;
    } else if (cfg->data_bits == DATA_BITS_7) {
        mr |= PS7_UART_MR_7_BIT;
    } else if (cfg->data_bits == DATA_BITS_6) {
        mr |= PS7_UART_MR_6_BIT;
    } else {
        qe_error("data bit:%d not support", cfg->data_bits);
        return qe_err_notsupport;
    }

    regs->mr = mr;

    /* baud rate */
    return ps7_uart_setbrg(uart->regs, uart->clk, cfg->baud_rate);
}

static void ps7_uart_isr_rx(qe_serial_dev *serial, ps7_uart_dev *uart, qe_u32 isr)
{
    qe_u8 data;
    qe_uint wait;
    ps7_uart_regs *regs = uart->regs;

    while (!(regs->sr & PS7_UART_SR_RXEMPTY)) {
        data = regs->fifo;
        qe_ringbuffer_write(serial->rx_ring, (char *)&data, 1);
    }

    wait = qe_ringbuffer_wait(serial->rx_ring);
    if ((isr & PS7_UART_IER_RTMOE) || wait > uart->rx_indicate_level) {
        if (serial->parent.rx_indicate) {
            serial->parent.rx_indicate(&serial->parent, wait);
        }
    }
}

static void ps7_uart_isr(void *ref)
{
	qe_u32 isr;

    ps7_uart_dev *uart = (ps7_uart_dev *)ref;
    qe_serial_dev *serial = &uart->serial;

    qe_assert(serial);
    qe_assert(uart);

    isr = uart->regs->imr;
    isr &= uart->regs->isr;
    qe_debug("isr:%x", isr);

    if (isr & (PS7_UART_IER_RTRIG | PS7_UART_IER_RFUL | PS7_UART_IER_ROVRE | PS7_UART_IER_RTMOE)) {
        //ps7_uart_irq_enable(uart->regs, qe_false);
        ps7_uart_isr_rx(serial, uart, isr);
        //ps7_uart_irq_enable(uart->regs, qe_true);
    }

    /* clear interrupt status */
    uart->regs->isr = isr;
}

static qe_ret ps7_uart_ioctl(qe_serial_dev *serial, int cmd, void *arg)
{
    ps7_uart_dev *uart = serial->parent.priv;

    qe_assert(serial != QE_NULL);
    qe_assert(uart != QE_NULL);

    switch (cmd) {

    case QE_DEV_IOC_CLR_INT:
        if (!uart->intc) {
            qe_error("%s not intc", serial->parent.name);
            return qe_err_notsupport;
        }
        qe_info("%s disable irq:%d", serial->parent.name, uart->irq);
        ps7_uart_irq_enable(uart->regs, qe_false);
        qe_intc_irq_disable(uart->intc, uart->irq);
        break;

    case QE_DEV_IOC_SET_INT:
        if (!uart->intc) {
            qe_error("%s not intc", serial->parent.name);
            return qe_err_notsupport;
        }
        qe_info("%s enable irq:%d", serial->parent.name, uart->irq);
        ps7_uart_irq_enable(uart->regs, qe_true);
        qe_intc_irq_enable(uart->intc, uart->irq);
        ps7_uart_set_rx_timeout(uart, uart->timeout);
        break;
    }

    return qe_ok;
}

static int ps7_uart_getc(qe_serial_dev *serial)
{
    int c;
    ps7_uart_dev *uart = (ps7_uart_dev *)serial->parent.priv;
    ps7_uart_regs *regs = uart->regs;

    qe_assert(serial != QE_NULL);
    qe_assert(uart != QE_NULL);

    c = -1;

    if (!(regs->sr & PS7_UART_SR_RXEMPTY))
        c = regs->fifo;

    return c;
}

static int ps7_uart_putc(qe_serial_dev *serial, char c)
{
    int timeout = 0;
    ps7_uart_dev *uart = (ps7_uart_dev *)serial->parent.priv;
    ps7_uart_regs *regs = uart->regs;

    qe_assert(serial != QE_NULL);
    qe_assert(uart != QE_NULL);

    while (regs->sr & PS7_UART_SR_TXFULL) {
        if (timeout++ > 1000000) {
            qe_error("ps7_uart_putc timeout");
            return -1;
        }
    }
    
    //xil_printf("put %c\r\n", c);
    regs->fifo = c;

    return 1;
}

static qe_uart_ops ps7_uart_ops = {
    .configure = ps7_uart_configure,
    .ioctl     = ps7_uart_ioctl,
    .getc      = ps7_uart_getc,
    .putc      = ps7_uart_putc,
};

static void serial_register(ps7_uart_dev *dev, qe_const_str name)
{
    qe_serial_configure config = QE_SERIAL_DEFCONFIG;

    config.bufsz = dev->buffer_size;
    config.baud_rate = dev->baudrate;

    dev->serial.config = config;
    dev->serial.ops = &ps7_uart_ops;

    qe_serial_register(&dev->serial, name,
        QE_DEV_F_RDWR|QE_DEV_F_INT_RX|QE_DEV_F_INT_TX, dev);
}

static qe_ret ps7_uart_probe(const void *fdt, int offset)
{
    int len;
    qe_ret ret;
    const char *name;
    const char *name_intc;
    const qe_u32 *prop_reg;
    const qe_u32 *prop_clk;
    const qe_u32 *prop_irq;
    const qe_u32 *prop_baud;
    const qe_u32 *prop_bufsz;
    const qe_u32 *prop_trigger;
    qe_intc_dev *intc;
    ps7_uart_dev *dev;

    name = fdt_get_name(fdt, offset, QE_NULL);
    qe_assert(name);

    prop_reg = (const qe_u32 *)fdt_getprop(fdt, offset, "reg", &len);
    if (!prop_reg) {
        qe_error("%s no reg prop", name);
        return qe_err_param;
    }

    prop_clk = (const qe_u32 *)fdt_getprop(fdt, offset, "clk", &len);
    if (!prop_clk) {
        qe_error("%s no clk prop", name);
        return qe_err_param;
    }

    prop_baud = (const qe_u32 *)fdt_getprop(fdt, offset, "baudrate", &len);
    if (!prop_baud) {
        qe_error("%s no baudrate prop", name);
        return qe_err_param;
    }

    name_intc = qe_of_get_interrupt_parent_name(fdt, offset);
    if (name_intc) {
        qe_debug("%s has interrupt parent %s", name_intc);
        intc = (qe_intc_dev *)qe_dev_find(name_intc);
        if (!intc) {
            qe_error("intc %s not found", name_intc);
            return qe_err_notfind;
        }
        prop_irq = (const qe_u32 *)fdt_getprop(fdt, offset, "interrupts", &len);
        if (!prop_irq) {
            qe_error("%s no interrupts prop", name);
            return qe_err_param;
        }
    }

    prop_trigger = (const qe_u32 *)fdt_getprop(fdt, offset, "trigger", &len);
    if (!prop_trigger) {
        qe_error("%s no trigger prop", name);
        return qe_err_param;
    }

    prop_bufsz = (const qe_u32 *)fdt_getprop(fdt, offset, "bufsz", &len);

    dev = qe_malloc(sizeof(ps7_uart_dev));
    qe_assert(dev);

    dev->regs = (ps7_uart_regs *)(qe_ubase)fdt32_to_cpu(*prop_reg);
    dev->clk = fdt32_to_cpu(*prop_clk);
    dev->baudrate = fdt32_to_cpu(*prop_baud);
    dev->rx_trigger_level = fdt32_to_cpu(prop_trigger[0]);
    dev->rx_indicate_level = fdt32_to_cpu(prop_trigger[1]);

    if (prop_bufsz) {
        dev->buffer_size = fdt32_to_cpu(*prop_bufsz);
    } else {
        dev->buffer_size = 64;
    }

    if (prop_irq) {
        dev->intc = intc;
        dev->irq = fdt32_to_cpu(prop_irq[1]);
        ret = qe_intc_irq_register(dev->intc, dev->irq, ps7_uart_isr, dev);
        if (ret != qe_ok) {
            qe_error("%s register irq %d err:%d", name, dev->irq, ret);
            qe_free(dev);
            return ret;
        }
        qe_debug("register irq:%d", dev->irq);
    }

    ps7_uart_hw_init(dev);

    serial_register(dev, name);

    qe_debug("register %s", name);

    return qe_ok;
}

static const qe_device_id ps7_uart_ids[] = {
    {.compatible = "xlnx,zynq-uart",},
    {}
};

QE_DRIVER(ps7_uart) = {
    .name = "ps7-uart",
    .of_match = ps7_uart_ids,
    .probe = ps7_uart_probe,
};

QE_DRIVER_FORCE_EXPORT(ps7_uart);

#if defined(CONFIG_PS7_UART_EARLYCON)
static void ps7_uart_earlycon_init(const void *fdt, int offset)
{
	int len;
    qe_u32 clk;
    qe_u32 baudrate;
    const qe_u32 *prop_reg;
    const qe_u32 *prop_clk;
    const qe_u32 *prop_baud;

    prop_reg = (const qe_u32 *)fdt_getprop(fdt, offset, "reg", &len);
    if (!prop_reg) {
        return;
    }

    prop_clk = (const qe_u32 *)fdt_getprop(fdt, offset, "clk", &len);
    if (!prop_clk) {
        return;
    }

    prop_baud = (const qe_u32 *)fdt_getprop(fdt, offset, "baudrate", &len);
    if (!prop_baud) {
        return;
    }

    ps7_uart_regs *reg = (ps7_uart_regs *)(qe_ubase)fdt32_to_cpu(*prop_reg);
    clk = fdt32_to_cpu(*prop_clk);
    baudrate = fdt32_to_cpu(*prop_baud);
    ps7_uart_setbrg(reg, clk, baudrate);
}

static void ps7_uart_earlycon_putc(qe_ubase base, char c)
{
    ps7_uart_regs *regs = (ps7_uart_regs *)base;
    
    while (regs->sr & PS7_UART_SR_TXFULL)
        ;
    
    regs->fifo = c;
}

QE_EARLYCON(ps7_uart) = {
    .compatible = "xlnx,zynq-uart",
    .init = ps7_uart_earlycon_init,
    .putc = ps7_uart_earlycon_putc,
};

QE_EARLYCON_FORCE_EXPORT(ps7_uart);
#endif